package org.mozhu.mboot.core.data;

import com.google.common.collect.Lists;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.mozhu.mboot.core.data.filter.GroupSearchFilter;
import org.mozhu.mboot.core.data.filter.LogicOperator;
import org.mozhu.mboot.core.data.filter.SearchFilter;
import org.mozhu.mboot.core.data.filter.SimpleSearchFilter;
import org.mozhu.mboot.core.util.ReflectionUtils;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;
import java.util.Collection;
import java.util.List;

@SuppressWarnings({"unchecked", "rawtypes"})
public class DynamicSpecifications {

    public static <T> Specification<T> bySearchFilter(final SearchFilter filter, final Class<T> entityClazz) {
        return bySearchFilter(Lists.newArrayList(filter), entityClazz);
    }

    public static <T> Specification<T> bySearchFilter(final Collection<SearchFilter> filters, final Class<T> entityClazz) {
        return new Specification<T>() {
            @Override
            public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
                if (CollectionUtils.isNotEmpty(filters)) {
                    return createPredicate(GroupSearchFilter.and(filters), root, query, builder);
                }
                return builder.conjunction();
            }

            private Predicate createPredicate(SimpleSearchFilter filter, Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
                // nested path translate, 如Task的名为"user.name"的filedName,
                // 转换为Task.user.name属性
                String propertyName = filter.getPropertyName();
                String[] names;
                if (propertyName.contains("#")) {
                    names = StringUtils.split(propertyName, "#");
                } else {
                    names = StringUtils.split(propertyName, ".");
                }
                Path expression = root.get(names[0]);
                for (int i = 1; i < names.length; i++) {
                    expression = expression.get(names[i]);
                }

                Class javaType = expression.getJavaType();
                if (filter.getValue() == null && filter.getValues() == null) {
                    return null;
                }
                // logic operator
                switch (filter.getOperator()) {
                    case EQ:
                        return builder.equal(expression, convertValue(javaType, filter.getValue()));
                    case NE:
                        return builder.notEqual(expression, convertValue(javaType, filter.getValue()));
                    case LIKE:
                        return builder.like(expression, "%" + filter.getValue() + "%");
                    case NLIKE:
                        return builder.notLike(expression, "%" + filter.getValue() + "%");
                    case PLIKE:
                        return builder.like(expression, filter.getValue() + "%");
                    case NPLIKE:
                        return builder.notLike(expression, filter.getValue() + "%");
                    case GT:
                        return builder.greaterThan(expression, (Comparable) convertValue(javaType, filter.getValue()));
                    case LT:
                        return builder.lessThan(expression, (Comparable) convertValue(javaType, filter.getValue()));
                    case GE:
                        return builder.greaterThanOrEqualTo(expression, (Comparable) convertValue(javaType, filter.getValue()));
                    case LE:
                        return builder.lessThanOrEqualTo(expression, (Comparable) convertValue(javaType, filter.getValue()));
                    case IN:
                        return expression.in(convertValues(javaType, filter.getValues()));
                    default:
                        break;
                }
                return null;
            }

            private Object convertValue(Class clazzType, Object value) {
                return ReflectionUtils.convertValue(value.toString(), clazzType);
            }

            private Collection<?> convertValues(Class clazzType, Object[] values) {
                Collection<Object> objects = Lists.newArrayList();
                for (Object value : values) {
                    objects.add(convertValue(clazzType, value.toString()));
                }
                return objects;
            }

            private Predicate createPredicate(SearchFilter filter, Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
                if (filter instanceof SimpleSearchFilter) {
                    return createPredicate((SimpleSearchFilter) filter, root, query, builder);
                }
                GroupSearchFilter groupSearchFilter = (GroupSearchFilter) filter;
                List<SearchFilter> searchFilters = groupSearchFilter.getSearchFilters();
                LogicOperator logicOperator = groupSearchFilter.getLogicOperator();
                List<Predicate> predicates = Lists.newArrayList();
                for (SearchFilter searchFilter : searchFilters) {
                    Predicate predicate = createPredicate(searchFilter, root, query, builder);
                    if (predicate != null) {
                        predicates.add(predicate);
                    }
                }
                if (!predicates.isEmpty()) {
                    if (LogicOperator.AND.equals(logicOperator)) {
                        return builder.and(predicates.toArray(new Predicate[predicates.size()]));
                    } else {
                        return builder.or(predicates.toArray(new Predicate[predicates.size()]));
                    }
                }
                return builder.conjunction();
            }
        };
    }
}
